#ifndef GRAPHICS_PERLINNOISE_H
#define GRAPHICS_PERLINNOISE_H

#include <core/object.h>

namespace yam3d
{
namespace graphics
{

    class PerlinNoise : public core::Object
    {
    public:
        enum InterpolationType
        {
            CLAMP,
            LINEAR,
            COSINE,
            CUBIC
        };

        /** Creates unsmoothed perlin noise with linear interpolation */
        explicit PerlinNoise( float persistence = 0.25f );
        virtual ~PerlinNoise();

        void setPersistence(float persistence);
        void setSmoothing(bool smoothed);
        void setInterpolation(InterpolationType interpolationType);

        float getNoiseValue(float x, int octave);
        float getNoiseValue(float x, float y, int octave);
        float getNoiseValue(float x, float y, float z, int octave);

    private:
        typedef int int32_t;

        float noise1(int32_t x);
        float noise2(int32_t x, int32_t y);
        float noise3(int32_t x, int32_t y, int32_t z);

        float smoothedNoise1(int32_t x);
        float smoothedNoise2(int32_t x, int32_t y);
        float smoothedNoise3(int32_t x, int32_t y, int32_t z);

        float (PerlinNoise::*m_noiseFunc1)(int32_t);
        float (PerlinNoise::*m_noiseFunc2)(int32_t,int32_t);
        float (PerlinNoise::*m_noiseFunc3)(int32_t,int32_t,int32_t);

        float (*m_interpolationFunc2)(float,float,float);
        float (*m_interpolationFunc4)(float,float,float,float,float);

        float m_persistence;
        float m_width;
        float m_height;

        float interpolatedNoise(float x, float (*interpolate)( float v0, float v1, float x ) );
        float interpolatedNoise(float x, float (*interpolate)( float v0, float v1, float v2, float v3, float x ) );

        float interpolatedNoise( float x, float y, float (*interpolate)( float v0, float v1, float x ) );
        float interpolatedNoise( float x, float y, float (*interpolate)( float v0, float v1, float v2, float v3, float x ) );

        float interpolatedNoise( float x, float y, float z, float (*interpolate)( float v0, float v1, float x ) );
        float interpolatedNoise( float x, float y, float z, float (*interpolate)( float v0, float v1, float v2, float v3, float x ) );

        float powerOf2( int o );
        float powerOfPersistence( int o );

        template< class InterpolateFuncType>
        float perlinNoise(float x, int octaves, InterpolateFuncType interpolateFunc )
        {
            float total(0.0f);
            for( int i=0; i<octaves; ++i)
            {
                float frequency = powerOf2(i);
                float amplitude = powerOfPersistence(i);

                total += interpolatedNoise(x * frequency, interpolateFunc) * amplitude;
            }
            return total;
        }

        template<class InterpolateFuncType>
        float perlinNoise(float x, float y, int octaves, InterpolateFuncType interpolateFunc )
        {
            float total(0.0f);
            for( int i=0; i<octaves; ++i)
            {
                float frequency = powerOf2(i);
                float amplitude = powerOfPersistence(i);

                total += interpolatedNoise(x * frequency, y * frequency, interpolateFunc) * amplitude;
            }
            return total;
        }

        template<class InterpolateFuncType>
        float perlinNoise(float x, float y, float z, int octaves, InterpolateFuncType interpolateFunc )
        {
            float total(0.0f);
            for( int i=0; i<octaves; ++i)
            {
                float frequency = powerOf2(i);
                float amplitude = powerOfPersistence(i);

                total += interpolatedNoise(x * frequency, y * frequency, z * frequency, interpolateFunc) * amplitude;
            }
            return total;
        }

        static float clampInterpolation( float a, float b, float x );
        static float linearInterpolation( float a, float b, float x );
        static float cosineInterpolation( float a, float b, float x );
        static float cubicInterpolation( float v0, float v1, float v2, float v3, float x );


       //PerlinNoise();
        PerlinNoise(const PerlinNoise&);
        PerlinNoise& operator=(const PerlinNoise&);
    };

}
}

#endif // GRAPHICS_PERLINNOISE_H
